/* Copyright (c) 2011 Alexandre Défossez, Timothée Lacroix
 * This file is part of HINAPT.
 *
 * HINAPT is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * HINAPT is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with HINAPT.  If not, see <http://www.gnu.org/licenses/>.
 */

 /*!
  \file utils.hpp
  Divers fonctions à-tout-faire utilisées dans tout le code.
 */

#ifndef UTILS_HPP_INCLUDED
#define UTILS_HPP_INCLUDED

#include "_hinapt.hpp"

/*!
 Vers l'INFINI et l'au delà.
*/
const double INFINI=1000*1000*1000;
/*!
  \param condition condition à tester
  \param s message d'erreur
  \return si condition est faux, lève une erreur avec le message s
*/
void Assert(bool cond, std::string s);

/*!
  Convertit une image OpenCV en une image SFML
  \param[in] source image à convetir
  \param[out] res l'image SFML

*/
void MatToSFML(Mat& source, sf::Image &res);

/*!
  Modifie en place source en la passant en niveau de gris
  et en lui appliquant une symétrie verticale

  \param[in,out] source image à transformer
*/
void normalize(Mat &a);

/*!
  Convertit img en niveau de gris en place
  \param[in,out] img image à modifier
*/
void toGrayScale(Mat &img);

/*!
  Calcul les images pyramidales succesives
  \param prev l'image actuelle
  \param[out] next l'image plus petite calculée à partir de prev
*/
void pyramidize(Mat &prev,Mat &next);

/*!
  Calcul la valeur d'un pixel qui se trouverait à la position
  décimale donnée, par interpolation des pixels voisins.
  \param img l'image auquel le pixel appartient
  \param x abscisse du pixel
  \param y ordonnée du pixel
  \return la valeur du pixel
*/
template<typename T> inline double getBrightness(Mat &img, double x, double y){
  int xi=x;
  int yi=y;
  double xd=x-xi;
  double yd=y-yi;
  return (1.-xd)*(1.-yd)*static_cast<double>(img.at<T>(yi,xi))+
         xd*(1.-yd)*static_cast<double>(img.at<T>(yi,xi+1))  +
         yd*(1.-xd)*static_cast<double>(img.at<T>(yi+1,xi))  +
         xd*yd*static_cast<double>(img.at<T>(yi+1,xi+1));

}

/*!
 Calcul le gradient de la matrice en (x,y)
 \param mat
 \param x
 \param y
 \return le gradient sous la forme d'un vecteur
*/
template<typename T> inline Vec2d gradient(Mat &mat, double x, double y){
  Vec2d d;
  d[0] = (getBrightness<T>(mat,x+1,y) - getBrightness<T>(mat,x-1,y)) / 2.;
  d[1] = (getBrightness<T>(mat,x,y+1) - getBrightness<T>(mat,x,y-1)) / 2.;
  return d;
}

/*!
 Calcul le gradient de la matrice en (x,y), version optimisée pour x et y
 entiers.
 \param mat
 \param x
 \param y
 \return le gradient sous la forme d'un vecteur
*/
template<typename T> inline Vec2d gradient(Mat &mat, int x, int y){
  Vec2d d;
  d[0] = (mat.at<T>(y, x+1) - mat.at<T>(y, x-1))/2.;
  d[1] = (mat.at<T>(y+1, x) - mat.at<T>(y-1, x))/2.;
  return d;
}



/*!
  Calcul le gradient sur toute la matrice
  \param[in] img la matrice dont il faut calculer le gradient
  \param[out] le gradient, chacune de ses cases est un Vec2d
*/
template<typename T> void calcGradient(Mat &img, Mat &grad){
  grad.create(img.size(), CV_64FC2);
  grad = Scalar(0);
  Vec2d d;
  for(int x=2; x<img.cols-2; x++){
    for(int y=2; y<img.rows-2; y++){
      grad.at<Vec2d>(y,x) = gradient<T>(img, x, y);
    }
  }
}

/*!
  Calcul la distance entre deux points
  \param a premier point
  \param b deuxième point
  \return la distance ab
*/
template<typename T> inline double distance(T a, T b){
  return sqrt((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y));
}

/*!
 Extrait une sous-image
 \param[in] a l'image d'origine
 \param[out] b la sous image
 \param center le centre de la zone à extraire
 \param size la taille (hauteur et largeur) de la zone à extraire
 \param clone spécifie s'il faut ou non copier les données
*/
void extract(Mat& a,Mat& b,Point2f center,int size=30, bool clone=false);
/*!
  Extrait une sous-image et la retourne
  \see extract(Mat& a,Mat& b,Point2f center,int size, bool clone)
  \return la sous-image
*/
Mat extract(Mat& a,Point2f center,int size=30, bool clone=false);


/*!
  Récupère une image depuis une source vidéo
  \param capture la source depuis laquelle il faut récupérer l'image
  \param[out] img la nouvelle image
*/
void getImage(VideoCapture &capture, Mat &img);

/*!
  Vérifie qu'un point est bien dans les limites imposées, sinon le déplace
  \param p le point à vérifier
  \param boundaries les limites dans lesquelles il doit se trouver
*/
bool inbound(Point2f &p, Rect boundaries);

/*!
  Accepte avec une certaine probabilité un changement. Plus delta est
  petit, plus la modification a des chances d'être acceptée
  \param delta le changement d'erreur
  \param deltaMax le changement maximal d'erreur possible
  \param param
  \return un booléan qui indique s'il faut ou non accepter le changement
*/
bool acceptChange(double delta,double deltaMax,double param=0.66);

/*!
  Affiche une matrice
  \param m la matrice à afficher
*/
void pprintMat(Mat &m);

/*!
  Calcul l'image cumulative de img
  \param img l'image à transformer
*/
void cumulate(Mat &img);

/*!
  Retourne le centre d'un rectange
  \param r
  \return le centre du rectangle
*/
Point2f center(Rect r);
/*!
  Convertit une valeur en string
  \param value valeur à convertir en string
  \return la string correspondant à la valeur
*/
template<typename T> std::string toString(T value){
  std::stringstream out;
  out << value;
  return out.str();
}

/*!
  Messages d'erreurs
*/
namespace errors{
  /*!
  La caméra ne peut pas être utilisée ou aucune caméra n'est présente
  */
  const std::string camera_failure = "impossible to use webcam";
}

/*!
  Résolution de la phase d'entrainement (images de nbTranche*nbTranche pixels)
*/
const int nbTranche=19;

/*!
  Nombre de type de features
*/
const int nbFeature=4;

/*!
  Taile de l'image d'entrée pour l'entrainement. Permet de dissocier taille de
  l'image et résolution
*/
const int pictSize=19;

/*!
  \param (x1,y1,x2,y2) rectangle dont on cherche le poids
  \param img image intégrale de l'image sur laquelle on calcul
  \return poids du rectangle sur l'image
*/
int evalRect(int x1,int y1,int x2,int y2,Mat img);

/*!
  \param (x1,y1,x2,y2,feature) feature à évaluer
  \param img image intégrale de l'image sur laquelle on calcul la feature
  \return évaluation de la feature
*/
int evalFeature(double x1,double y1,double x2,double y2,
                int feature,Mat img);

/*!
  \param[in,out] weights vecteur de poids à normaliser
*/
void normalize(vector<double> &weights);

#endif // UTILS_HPP_INCLUDED
